import path from 'path';

import { entryBuilder } from '../module-builder';

import {
  APIVersion,
  compileVersion,
  doubleSdkPath,
  ModuleTemplateType,
  nodePath,
  SDKType,
  singleSdkPath
} from './constants';
import { ProjectTemplateOption } from './model-options';
import { Module } from './module-models';
import { AbsFileNode, DataNode, DirNode, DirTemplate } from './template-models';

/**
 *
 * @type {{[APIVersion.API10]: (name, option, modules, template) => Promise<void>}}
 */
const defaultInitFunMap: Partial<{ [key in APIVersion]: ProjectConfigInitFun }> = {
  [APIVersion.API10]: async (name, option, modules, template) => {
    template.node('build-profile.json5').data = {
      apiVersion: compileVersion(option.apiVersion, option.runtimeOS),
      runtimeOS: option.runtimeOS,
      modules: Object.values(modules).map(module => {
        return {
          moduleName: module.name,
          hasTarget: module.option.moduleTemplateType !== ModuleTemplateType.StaticLibrary
            && module.option.moduleTemplateType !== ModuleTemplateType.VisualLibrary
        };
      })
    };

    template.node('oh-package.json5').data = {
      projectName: name
    };
  }
};

export class Project {

  /**
   * 工程名称
   */
  readonly name: string;

  /**
   * 模板标签
   *
   * @type {ProjectTemplateOption}
   */
  readonly option: ProjectTemplateOption;

  /**
   * 模板对象
   *
   * @type {DirTemplate}
   */
  readonly template: DirTemplate;

  /**
   * 构建完成后生成的文件对象
   *
   * @type {DirNode<AbsFileNode<any, any>, any>}
   */
  public dirNode?: DirNode<AbsFileNode<any>>;

  /**
   * 配置初始化的方法
   *
   * @type {ProjectConfigInitFun}
   * @private
   */
  private readonly configInitFun: ProjectConfigInitFun;

  /**
   * 子模块
   *
   * @type {{[p: string]: Module}}
   * @private
   */
  private readonly modules: { [moduleName: string]: Module } = {};

  /**
   * 构造函数
   *
   * @param {DirTemplate} template
   * @param {ProjectTemplateOption} option
   * @param {ProjectConfigInitFun} init
   */
  constructor(template: DirTemplate, option: ProjectTemplateOption, init?: ProjectConfigInitFun) {
    this.name = template.name;
    this.template = template;
    this.option = option;
    this.configInitFun = this.initFunProxy(init);

    // 初始化默认的entry模块
    const entryOption = {
      runtimeOS: this.option.runtimeOS,
      apiVersion: this.option.apiVersion,
      apiType: this.option.apiType,
      moduleTemplateType: this.option.entryTemplateType,
      language: this.option.language,
      abilityType: this.option.abilityType
    };
    const findModuleCreator = entryBuilder.findModuleCreator(entryOption);
    if (findModuleCreator === undefined) {
      throw new Error(`The matching module template does not exist: ${JSON.stringify(entryOption)}`);
    }
    this.modules['entry'] = findModuleCreator('entry', entryOption);
  }

  /**
   * 获取工程目录
   *
   * @return {string}
   */
  public projectRootPath(): string {
    return this.dirNode!.path!;
  }

  /**
   * 构建module
   *
   * @param {string} target
   * @return {Promise<void>}
   */
  async build(target: string): Promise<void> {
    // 初始化配置
    await this.configInitFun(this.name, this.option, this.modules, this.template as DataNode<DataNode<any>>);

    // 生成主目录下的配置文件
    this.dirNode = await this.template.generated(target);
    const projectPath = path.join(target, this.name);

    // 生成子模块
    await Promise.all(Object.values(this.modules).map(item => item.build(projectPath)));

    this.build = (target: string) => {
      throw new Error('The file has finished building and the function cannot be called again.');
    };
  }

  /**
   * 刷新文件
   *
   * @return {Promise<void>}
   */
  async refresh(update?: ProjectRefreshFun): Promise<void> {
    if (this.dirNode === undefined) {
      throw new Error('The file has not been built, please execute the build() function.');
    }

    // 初始化配置
    await this.configInitFun(this.name, this.option, this.modules, this.dirNode as DataNode<DataNode<any>>);

    if (update) {
      await update(this.name, this.option, this.dirNode);
    }

    // 刷新主目录下的配置文件
    await this.dirNode!.refresh();

    // 刷新子模块
    await Promise.all(Object.values(this.modules).map(item => item.refresh()));
  }

  /**
   * 添加子模块
   *
   * @param {string} moduleName
   * @param {Module} module
   */
  public addModule(moduleName: string, module: Module): void {
    this.modules[moduleName] = module;
  }

  /**
   * 获取子模块
   *
   * @param {string} moduleName
   * @return {Module}
   */
  public getModule(moduleName: string): Module {
    return this.modules[moduleName];
  }

  /**
   * 获取子模块名称
   *
   * @return {string[]}
   */
  public getModuleNames(): string[] {
    return Object.values(this.modules).map(module => module.name);
  }

  private initFunProxy(initFun?: ProjectConfigInitFun): ProjectConfigInitFun {
    const fun = initFun ?? defaultInitFunMap[this.option.apiVersion];
    if (!fun) {
      throw new Error('template project lack default configuration initialization function');
    }
    return async (name, option, modules, template) => {
      const commonOption = {
        runtimeOS: this.option.runtimeOS,
        apiVersion: this.option.apiVersion,
        apiType: this.option.apiType,
        language: this.option.language,
        abilityType: this.option.abilityType
      };

      // 将工程的配置项同步到模块
      Object.values(modules).forEach(module => module.refreshOption(commonOption));

      template.node('local.properties').data = {
        sdkPath: option.sdk === SDKType.Single ? singleSdkPath : doubleSdkPath,
        nodePath: nodePath
      };
      await fun(name, option, modules, template);
    };
  }
}


/**
 * 配置初始化函数
 */
export type ProjectConfigInitFun = (name: string, option: ProjectTemplateOption, modules: {
    [moduleName: string]: Module
  },
  template: DataNode<DataNode<any>>) => Promise<void>;

/**
 * 配置刷新函数
 */
export type ProjectRefreshFun = (name: string, option: ProjectTemplateOption, dirNode: DirNode<AbsFileNode<any>>) => Promise<void>;
